package com.mimo.logic.user.service.impl;

import java.text.MessageFormat;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import com.mimo.common.comet.dto.user.UserLoginReq;
import com.mimo.common.logic.code.StatusCode;
import com.mimo.common.utils.RandomStringUtils;
import com.mimo.logic.blocking.constants.DenyOperation;
import com.mimo.logic.blocking.service.IAccessiableService;
import com.mimo.logic.metric.constants.MetricType;
import com.mimo.logic.metric.service.IMetricService;
import com.mimo.logic.user.constants.UserKeys;
import com.mimo.logic.user.dao.IDeviceDao;
import com.mimo.logic.user.dao.IUserDao;
import com.mimo.logic.user.dao.IZoneDao;
import com.mimo.logic.user.model.DeviceInfo;
import com.mimo.logic.user.model.UserInfo;
import com.mimo.logic.user.model.ZoneInfo;
import com.mimo.logic.user.service.IUserService;

@Service
public class UserServiceImpl implements IUserService {

  @Autowired
  private IMetricService metricService;

  @Autowired
  private IDeviceDao deviceDao;

  @Autowired
  private IZoneDao zoneDao;

  @Autowired
  private IUserDao userDao;

  @Autowired
  private IAccessiableService userAccessiableService;

  @Autowired
  private RedisTemplate<String, String> redisTemplate;

  @Override
  public UserInfo load(String uid) {
    return userDao.findById(uid)
        .orElseThrow(() -> new IllegalArgumentException(String.format("UserId:%s not found", uid)));
  }

  @Override
  public UserInfo generate(String uid) {
    UserInfo u = null;
    String token = RandomStringUtils.uniqueRandom();
    Optional<UserInfo> opt = userDao.findById(uid);
    if (opt.isPresent()) {
      u = opt.get();
      u.setToken(token);
      userDao.save(u);
    } else {
      u = new UserInfo();
      u.setUid(uid);
      u.setToken(token);
      u.setCreatedDate(new Date());
      userDao.insert(u);

      // 填充维护一个已有的用户桶
      saveToUserBucket(uid);

      // 新增用户维度累计
      metricService.accumulate(MetricType.User_Create, uid);
    }
    return u;
  }

  @Override
  public StatusCode login(UserLoginReq req) {
    StatusCode code = userAccessiableService.checkAccessiable(req.getUid(), null, DenyOperation.GlobalBlock); // 登陆前先判断其是否允许访问,即封禁检查

    if (code.isSuccess()) {
      Optional<UserInfo> opt = userDao.findById(req.getUid());
      if (opt.isPresent()) {
        UserInfo u = opt.get();
        boolean isValid = Objects.equals(u.getToken(), req.getToken());
        if (isValid) {

          // 维护持久化用户的登陆时的区域信息
          ZoneInfo zone = zoneDao.findById(req.getUid()).orElseGet(() -> {
            ZoneInfo tz = new ZoneInfo();
            tz.setId(req.getUid());
            return tz;
          });
          BeanUtils.copyProperties(req.getZone(), zone);
          zoneDao.save(zone);

          // 维护持久化用户登录时的设备信息
          DeviceInfo device = deviceDao.findById(req.getUid()).orElseGet(() -> {
            DeviceInfo d = new DeviceInfo();
            d.setId(req.getUid());
            return d;
          });
          BeanUtils.copyProperties(req.getDevice(), device);
          deviceDao.save(device);

          // 添加用户最后绑定的客户端数据
          u.setLoginIp(req.getIp());
          u.setTerminal(req.getTerminal());
          u.setVersion(req.getVersion());
          u.setOnline(true);
          u.setZone(zone);
          u.setLoginDate(new Date());
          u.setDeviceId(req.getDeviceId());
          u.setDevice(device);
          userDao.save(u);
          code = StatusCode.Success;

          // 用户登陆维度统计
          metricService.accumulate(MetricType.User_Login, req.getUid());
          // 活跃用户维护统计
          metricService.accumulate(MetricType.User_Active, req.getUid());
          // 保存至redis
          saveToUserBucket(req.getUid());
        } else {
          code = StatusCode.LoginFailed;
        }

      } else {
        code = StatusCode.LoginFailed;
      }
    }

    return code;
  }

  @Override
  public boolean isValid(String uid) {
    int slot = Math.abs(uid.hashCode() % MAX_USER_SLOT);
    String key = MessageFormat.format(UserKeys.LOGIC_USER_BUCKET_PATTERN, slot);
    return Objects.nonNull(redisTemplate.opsForZSet().rank(key, uid));
  }

  @Override
  public StatusCode logout(String uid) {
    Optional<UserInfo> opt = userDao.findById(uid);
    if (opt.isPresent() && opt.get().isOnline()) {
      opt.get().setOnline(false);
      userDao.save(opt.get());
    }
    return StatusCode.Success;
  }

  private void saveToUserBucket(String uid) {
    int slot = Math.abs(uid.hashCode() % MAX_USER_SLOT);
    String key = MessageFormat.format(UserKeys.LOGIC_USER_BUCKET_PATTERN, slot);
    redisTemplate.opsForZSet().add(key, uid, System.currentTimeMillis());
  }

}
